//
//  BBTUIODispatchDelegate.m
//  xNose
//
//  Created by ben smith on 25/09/08.
//  Copyright 2008 __MyCompanyName__. All rights reserved.
//

#import "BBTUIODispatchDelegate.h"
#import "BBOSC.h"
#import "BBTUIOCursor.h"
#import "BBNodeController.h"
#import "BBGestureDecoder.h"
#import "BBAppController.h"


@implementation BBTUIODispatchDelegate

@synthesize currentMouseCursor;

- (id) init
{
	self = [super init];
	if (self != nil) {
		// setup my storage
		activeBlobs = [[NSMutableDictionary alloc] init];
		gestureDecoder = [[BBGestureDecoder alloc] init];
		currentEvents = [[NSMutableArray alloc] init];
	}
	return self;
}

// this is called by the OSCListener with the raw packet data
-(void)dispatchRawPacket:(NSData*)someData
{
	// all we really care about right now are TUIO/2DCur messages
	// these come in bundles, so any non-bundle will be ignored
	id decodedPacket = [BBOSCDecoder decodeData:someData];
	if (![decodedPacket isKindOfClass:[BBOSCBundle class]]) return;
	
	// decompress the bundle into it's contained messages, 
	// dispatch each one individually
	
	for (BBOSCMessage * message in [decodedPacket attachedObjects]) {
		[self dispatchMessage:message];
	}
}

// this is a specific implementation designed for the TUIO/2DCur messages
// the come in four types: (and in this order, generally)

// source - defines where the packets are coming from
// set - this adds new 'events', could be old blob ids or new ones
// alive - this tells us which blobs (including the ones just set) that are still alive
// fseq - this signals the end of this frame, and gives a sequence number, in theory the frames can come out of order, and we should be looking for that, but not for this app

-(void)dispatchMessage:(BBOSCMessage*)message
{
	// need to figure out what kind of message it is
	// lowercase all the strings to make it easier to match up
	NSString * addy = [[[message address] address] lowercaseString];
	
	// for now, just check to make sure it is what we think it is
	// not a TUIO/2DCur? then ignore it
	if (![addy isEqualToString:@"/tuio/2dcur"]) return;
	// now for the 'command'
	NSString * command = [[[[message attachedObjects] objectAtIndex:0] stringValue] lowercaseString];
	
	// a bit of manual hackery. this could be nicer
	if ([command isEqualToString:@"fseq"]) {
		[self processFseq:[message attachedObjects]];
		return;
	}
	if ([command isEqualToString:@"alive"]) {
		[self processAlive:[message attachedObjects]];
		return;
	}
	if ([command isEqualToString:@"source"]) {
		//		[self processSource:[message attachedObjects]];
		return;
	}
	if ([command isEqualToString:@"set"]) {
		[self processSet:[message attachedObjects]];
		return;
	}
}

// just a nice quick way to grab the identified cursors out of a 
// dictionary
-(NSString*)keyForID:(NSInteger)num
{
	// just the ID in string form
	return [NSString stringWithFormat:@"%d",num];
}


// the set either adds a cursor or updates an existing cursor
// if we use the id as a key, then we just need to 
// set the object in a dictionary and if the key already exists
// then it will overwrite, otherwise it will just add it
// NOTE: if we cared about 'blobDown' style events we would want to 
// check to see if the key is already in the dictionary, but for now we dont
-(void)processSet:(NSArray*)args
{
	// make a enw cursor from the arg array
	BBTUIOCursor * cursor = [[BBTUIOCursor alloc] initWithArgs:args];
	// put it into the acitve blob collection
	if ([activeBlobs objectForKey:[self keyForID:[cursor cursorID]]] == nil) {
		// then it is a new blob
		cursor.isNew = YES;
	} else {
		// no longer new, now old and boring
		cursor.isNew = NO;
	}
	[activeBlobs setObject:cursor forKey:[self keyForID:[cursor cursorID]]];
	[cursor release];
}

// this tells us which blobs are still on the board
// we need to check against all our active blobs and remove any that
// are not in the alive message
// NOTE: this is where you would synthesize blobUp style event, if you wanted to
-(void)processAlive:(NSArray*)args
{
	// we are going to put all the still breathing blobs into
	// a temporary storage, then clear the active blobs
	// then put the still breathing ones back into the active list
	NSMutableDictionary * blobStorage = [NSMutableDictionary dictionary];
	NSString * key; 
	int i;
	// ignore the first arg, it is the 'set' string
	for (i = 1; i < [args count]; i++) {
		// get the key from the arg list
		key = [self keyForID:[[args objectAtIndex:i] intValue]]; 
		// grab the cursor out of the active list and add it to the storage
		id cursor = [activeBlobs objectForKey:key];
		if (cursor != nil) {
			[blobStorage setObject:cursor forKey:key];			
		} 
	}
	// any blobs left in the storage get obliterated here
	[activeBlobs removeAllObjects];
	// move all the still breathing blobs back into storage
	[activeBlobs addEntriesFromDictionary:blobStorage];
}

// this is the frame sequence.  in theory we should be checking to make sure that 
// all the events arrive at the proper time. but since this is visual control
// instead of sound, we will blow it off for now
// however, we will use it as an 'end of frame' event
-(void)processFseq:(NSArray*)args
{
	// dont really care about the sequence right now
	// so just set the view blobs and that is all
	[self decodeCursors];
	NSArray * cursors = [[NSArray alloc] initWithArray:[activeBlobs allValues] copyItems:YES];
	// send off the event array 
//	if ([cursors count] > 0)
	[[NSNotificationCenter defaultCenter] postNotificationName:@"BBTUIOCursors" object:cursors];

	[cursors release];
	
	[currentEvents removeAllObjects];
}

// ehh.. we dont really care about the source.  maybe someday, but not today
-(void)processSource:(NSArray*)args
{
	// dont really care about the source right now
	
}




-(void)handleMouseUp:(BBTUIOCursor*)cursor
{
	// need to make a new event, and then post it
	// via the nsapplication
	// figure out the location
	NSRect frame = [[[BBNodeController sharedNodeController] currentRootView] frame];
	NSPoint location = [cursor position];
	
	location.x *= NSWidth(frame);
	location.y = NSHeight(frame) - (location.y * NSHeight(frame));
	
	// figure out the window number
	NSInteger winNum = [[[[BBNodeController sharedNodeController] currentRootView] window] windowNumber];
	
	NSEvent * newMouseEvent = [NSEvent mouseEventWithType:NSOtherMouseUp location:location modifierFlags:0 timestamp:[NSDate timeIntervalSinceReferenceDate] windowNumber:winNum context:nil eventNumber:[cursor cursorID] clickCount:1 pressure:0.0];
	[[[[BBNodeController sharedNodeController] currentRootView] window] sendEvent:newMouseEvent];
	[currentEvents addObject:newMouseEvent];
	//[NSApp postEvent:newMouseEvent atStart:YES];
}



-(void)handleMouseDown:(BBTUIOCursor*)cursor
{
	// need to make a new event, and then post it
	// via the nsapplication
	// figure out the location
	NSRect frame = [[[BBNodeController sharedNodeController] currentRootView] frame];
	NSPoint location = [cursor position];

	location.x *= NSWidth(frame);
	location.y = NSHeight(frame) - (location.y * NSHeight(frame));
	
	// figure out the window number
	NSInteger winNum = [[[[BBNodeController sharedNodeController] currentRootView] window] windowNumber];
	
	NSEvent * newMouseEvent = [NSEvent mouseEventWithType:NSOtherMouseDown location:location modifierFlags:0 timestamp:[NSDate timeIntervalSinceReferenceDate] windowNumber:winNum context:nil eventNumber:[cursor cursorID] clickCount:1 pressure:0.0];
	[[[[BBNodeController sharedNodeController] currentRootView] window] sendEvent:newMouseEvent];
	[currentEvents addObject:newMouseEvent];
//	[NSApp postEvent:newMouseEvent atStart:YES];
}


-(void)handleMouseDrag:(BBTUIOCursor*)cursor
{
	// need to make a new event, and then post it
	// via the nsapplication
	// figure out the location
	
	NSRect frame = [[[BBNodeController sharedNodeController] currentRootView] frame];
	NSPoint location = [cursor position];
	
	location.x *= NSWidth(frame);
	location.y = NSHeight(frame) - (location.y * NSHeight(frame));
	
	// figure out the window number
	NSInteger winNum = [[[[BBNodeController sharedNodeController] currentRootView] window] windowNumber];
		
	NSEvent * newMouseEvent = [NSEvent mouseEventWithType:NSOtherMouseDragged location:location modifierFlags:0 timestamp:[NSDate timeIntervalSinceReferenceDate] windowNumber:winNum context:nil eventNumber:[cursor cursorID] clickCount:1 pressure:1.0];
	[[[[BBNodeController sharedNodeController] currentRootView] window] sendEvent:newMouseEvent];
	[currentEvents addObject:newMouseEvent];
//	[NSApp postEvent:newMouseEvent atStart:YES];
}


-(void)decodeCursors
{
	// all blobs stored in: [activeBlobs allValues]
	// four possibilities that we want to catch and handle:
	// mouse down: the mouseid = -1 and we have a new cursor
	// mouse drag: the cursor matches the mouseid
	// mouse up: the actives dont contain any cursors that match our mouseid
	// pinch-scale: two cursors down and dragging
	
	if ([[activeBlobs allValues] count] == 0) {
		// check to see if we have a mouse up
		if (self.currentMouseCursor != nil) [self handleMouseUp:self.currentMouseCursor];
	//	[[BBAppController sharedAppController] setCurrentTouchPosition:NSZeroPoint];
		self.currentMouseCursor = nil;
		return;
	}
	
	// the way this is done it should effective ignore if there are more than one point
	// on the table, and only deal with the first one.  it will switch to the next one 
	// when the first goes away.
	if ([[activeBlobs allValues] count] == 1) {
		// is it a new cursor?
		if (self.currentMouseCursor == nil) {
			// new cursor, mousedown me
			// just grab the first one, if there are more than one
			BBTUIOCursor * thisCursor = [[activeBlobs allValues] objectAtIndex:0];
			self.currentMouseCursor = thisCursor;
			[self handleMouseDown:self.currentMouseCursor];
		//	NSLog(@"zz: %@",NSStringFromPoint(thisCursor.position));
			//[[BBAppController sharedAppController] setCurrentTouchPosition:thisCursor.position];
			return;
		}
		//it could be a drag? or a new cursor (mouseup)
		// if the id's match it is a drag, otherwise it is a mouseup
		id currentkey = [self keyForID:self.currentMouseCursor.cursorID]; 
		BBTUIOCursor * thisCursor = [activeBlobs objectForKey:currentkey];
		if (thisCursor == nil) {
			// mouseup
			[self handleMouseUp:self.currentMouseCursor];
			self.currentMouseCursor = nil;
			return;			
		}
		// drag
		if ((thisCursor.position.x != currentMouseCursor.position.x) || (thisCursor.position.y != currentMouseCursor.position.y)) {
			[self handleMouseDrag:thisCursor];
			self.currentMouseCursor = thisCursor;			
		} 
//		[[BBAppController sharedAppController] setCurrentTouchPosition:thisCursor.position];
	}
	// check for two cursors
	if ([[activeBlobs allValues] count] > 1) {
		// we only care about this if they are both hitting the underlay view
		if ([self handleMultiTouch]) return;
		// otherwise just look at the one point and ignore the others
	}
}

-(BOOL)handleMultiTouch
{
	// first: if they are not both in the underlay view, then I dont care
	// load all the cursors into teh decoder, let it decide if it is a scale event
	NSRect frame = [[[BBNodeController sharedNodeController] currentRootView] frame];
	
	for (BBTUIOCursor * cursor in [activeBlobs allValues]) {
		// if it is in the underlay, then it is ripe for decoding
		NSPoint location = [cursor position];		
		location.x *= NSWidth(frame);
		location.y = NSHeight(frame) - (location.y * NSHeight(frame));
		NSView * hitView = [[BBNodeController sharedNodeController] hitTest:location];
		if (hitView == nil)	{
			[cursor setScreenPosition:location];
			[gestureDecoder addCursor:cursor];			
		}
	}
	[gestureDecoder finishCursorCycle];
	if (gestureDecoder.eventMask & BBScaleEventMask) {
		float globalScale = [[BBNodeController sharedNodeController] scale];
		[[NSNotificationCenter defaultCenter] postNotificationName:@"BBZoomNotification" object:[NSNumber numberWithFloat:(globalScale * gestureDecoder.scale)]];

		return YES;		
	}
	
	// OK, now check for a drag event
	
	return NO;
}

@end
